// button.c
//
// Purpose: Implements the generic button driver's core logic.
// This includes the button state machine, debounce mechanism,
// and calling event callbacks.
//
// System Thinking: Robustly handles multiple button inputs with a unified approach.
// Module Thinking: Encapsulates button states and logic, interacts with HW HAL for pin reads.
// Computational Thinking: Implements a finite state machine algorithm for debounce and event detection.

#include "button.h"
#include <string.h>     // For memset, strcmp
#include "xil_printf.h" // For debug printing

// FreeRTOS critical section macros for protecting shared resources (like the button pool)
#define BUTTON_ENTER_CRITICAL() taskENTER_CRITICAL()
#define BUTTON_EXIT_CRITICAL()  taskEXIT_CRITICAL()

// Internal button state machine definitions
// These states represent the progression of a button press and release,
// incorporating debounce logic.
enum ButtonState {
    STATE_IDLE = 0,             // Button is not pressed, waiting for a press
    STATE_DEBOUNCE_PRESS,       // Button is pressed, but debouncing is in progress
    STATE_PRESSED,              // Button has been stably pressed (debounced)
};

// Global pool to hold pointers to all registered button instances.
// This is a shared resource, accessed by button_register and button_process.
static Button_t* g_button_pool[BUTTON_MAX_NUM] = {NULL};
// Counter for the number of currently registered buttons.
static u8 g_button_count = 0;

// Compile-time check to ensure a valid scan interval is configured.
#if BUTTON_SCAN_INTERVAL_MS == 0
    #error "BUTTON_SCAN_INTERVAL_MS cannot be 0, please check your configuration in button.h!"
#endif


/**
 * @brief Core function: State machine processing logic for a single button.
 *        This function is called periodically for each registered button to update its state.
 * @param button Pointer to the Button_t object to process.
 *
 * Computational Thinking: Implements a classic finite state machine to manage button inputs,
 *  handling noise (debounce) and detecting specific events (click).
 */
static void handle_button_state(Button_t* button)
{
    // Defensive check
    if (button == NULL) {
        return;
    }

    // Read the current physical level of the button pin using the HAL.
    // Casting to u8 to match button->pressed_level type.
    u8 current_level = (u8)HAL_GPIO_ReadPin(button->pin);

    switch (button->state)
    {
        case STATE_IDLE:
            // If the button is pressed (matches its active level), move to debounce state.
            if (current_level == button->pressed_level)
            {
                button->state = STATE_DEBOUNCE_PRESS;
                button->debounce_cnt = 0; // Reset debounce counter
            }
            break;

        case STATE_DEBOUNCE_PRESS:
            // If the button remains pressed, increment debounce counter.
            if (current_level == button->pressed_level)
            {
                button->debounce_cnt++;
                // If debounce counter reaches threshold, the press is stable.
                if (button->debounce_cnt >= BUTTON_DEBOUNCE_TICKS)
                {
                    button->state = STATE_PRESSED;
                    // No long press logic in this version.
                }
            }
            else
            {
                // If the button is released during debounce, it was just noise. Reset to IDLE.
                button->state = STATE_IDLE;
            }
            break;

        case STATE_PRESSED:
            // Button is stably pressed. Wait for release.
            if (current_level != button->pressed_level) // Button released
            {
                button->state = STATE_IDLE; // Reset to IDLE state.
                // Trigger the click callback, as a stable press was followed by a release.
                if (button->callbacks[BUTTON_EVENT_CLICK] != NULL)
                {
                    button->callbacks[BUTTON_EVENT_CLICK](button);
                }
            }
            // If still pressed, do nothing, continue to wait for release.
            break;

        default:
            // Should not happen, but a defensive measure to reset state.
            button->state = STATE_IDLE;
            break;
    }
}


/*========================= API Functions Implementation =========================*/

/**
 * @brief Registers a button with the driver management pool.
 */
int button_register(Button_t* button, u32 pin, u8 pressed_level, const char* name)
{
    // Validate input parameters for robustness.
    if (button == NULL || name == NULL || (pressed_level != BUTTON_ACTIVE_LOW && pressed_level != BUTTON_ACTIVE_HIGH))
    {
        xil_printf("button_register: Invalid parameters for button %s.\r\n", name ? name : "UNKNOWN");
        return -1;
    }

    // Protect the shared button pool from concurrent access by other tasks.
    BUTTON_ENTER_CRITICAL();
    if (g_button_count >= BUTTON_MAX_NUM)
    {
        BUTTON_EXIT_CRITICAL(); // Ensure critical section is always exited.
        xil_printf("button_register: Button pool is full. Max %d buttons.\r\n", BUTTON_MAX_NUM);
        return -1;
    }

    // Initialize the button structure. memset to 0 ensures all fields are clean.
    memset(button, 0, sizeof(Button_t));
    button->pin = pin;
    button->pressed_level = pressed_level;
    button->name = name;
    button->state = STATE_IDLE; // Initialize state to IDLE.

    // Add the button to the global pool and increment count.
    g_button_pool[g_button_count++] = button;
    BUTTON_EXIT_CRITICAL();
    return 0;
}

/**
 * @brief Retrieves a registered button instance by its name.
 */
Button_t* Button_GetByName(const char* name)
{
    if (name == NULL) return NULL;

    // Protect the shared button pool during lookup.
    BUTTON_ENTER_CRITICAL();
    for (u8 i = 0; i < g_button_count; i++)
    {
        // Compare names of registered buttons.
        if (g_button_pool[i] != NULL && strcmp(g_button_pool[i]->name, name) == 0)
        {
            BUTTON_EXIT_CRITICAL();
            return g_button_pool[i];
        }
    }
    BUTTON_EXIT_CRITICAL();
    return NULL; // Button not found.
}


/**
 * @brief Sets the click event callback function for a button instance.
 */
void Button_SetClickCallback(Button_t* button_instance, Button_Event_Callback on_click_cb)
{
    // Ensure the button instance is valid before attempting to set its callback.
    if (button_instance != NULL)
    {
        // No critical section needed here, as button->callbacks array is set once during init
        // and callbacks are typically registered before scheduler starts or are stable.
        button_instance->callbacks[BUTTON_EVENT_CLICK] = on_click_cb;
    }
}

/**
 * @brief Processes the state of all registered buttons.
 *        This is the main entry point for the button driver's periodic operation.
 */
void button_process(void)
{
    // Iterate through all registered buttons and process their state machines.
    // Critical section added for robustness and to protect against potential
    // concurrent modification of g_button_pool/g_button_count in more dynamic scenarios.
    BUTTON_ENTER_CRITICAL();
    for (u8 i = 0; i < g_button_count; i++)
    {
        if (g_button_pool[i] != NULL)
        {
            handle_button_state(g_button_pool[i]);
        }
    }
    BUTTON_EXIT_CRITICAL();
}

